<template>
  <common-page
    class="add-view-board"
    :title="`${
      isEdit ? '编辑看板-' + viewBoardName : '新增看板'
    }（所属分类：${typeName}）`"
  >
    <div slot="header-content" class="header-edit-name">
      <span class="line">|</span>
      <span class="label">看板名称</span>
      <common-input
        v-model="form.name"
        placeholder="请输入看板名称"
        :maxlength="16"
        clearable
      ></common-input>
    </div>
    <el-button slot="header" @click="$emit('close')">返回</el-button>
    <el-button
      slot="header"
      type="primary"
      @click="submitForm"
      :loading="loading"
      >保存</el-button
    >

    <div
      class="add-view-board-wrap"
      slot="content"
      v-loading="isPageDataLoading"
    >
      <!-- 左侧报表组件区域 -->
      <div class="component-tree-wrap">
        <div class="component-tree-label">报表组件</div>
        <div class="component-tree">
          <common-tree
            :treeLoading="treeLoading"
            slot="group"
            :treeData="menuTree"
            :treeProp="treeProp"
            :searchPlaceholder="'查询组件'"
            isSlot
            searchForChildren
            :highlightCurrent="false"
          >
            <div
              class="tree-node-item"
              style="width: 100%"
              slot-scope="{ scope }"
              @mousedown="mousedown(scope.data)"
              @mouseup="mouseup"
            >
              <svg aria-hidden="true" class="icon" v-if="scope.data.chartType">
                <use
                  :xlink:href="`#icon-${getIcon(scope.data.chartType)}`"
                ></use>
              </svg>
              <common-carousel-text
                :style="{
                  width: scope.data.chartType ? 'calc(100% - 18px)' : '100%',
                }"
                :content="scope.node.label"
                :index="scope.node.id"
                :font-size="14"
              >
              </common-carousel-text>
            </div>
          </common-tree>
        </div>
      </div>
      <!-- 右侧拖拽展示区域 -->
      <div class="view-board-right-wrap">
        <div class="view-board-title">
          <el-button
            class="config-btn"
            type="text"
            icon="iconfont icon-a-0_canshu_canshushezhi"
            size="normal"
            @click="setGlobalOption"
          >
            <span>全局配置</span>
          </el-button>
          <el-button
            class="config-btn button-view"
            type="text"
            icon="iconfont icon-ai"
            :disabled="!isShowButton"
            size="normal"
            @click="setAILayout"
          >
            <span>布局</span>
          </el-button>
          <!--          <div class="config-btn" @click="setAILayout" :class="{'config-btn-disabled': !isShowTip}">-->
          <!--            <commonIcon iconName="icon-ai" iconType="icon" :iconSize="14" isStyle></commonIcon>-->
          <!--            <span>布局</span>-->
          <!--          </div>-->
        </div>
        <div class="view-board-main" ref="vbMain">
          <div class="vb-main-container" ref="vbContainer">
            <component
              v-for="component in configData"
              :is="component.type"
              :key="component.id"
              :isPreview="isPreview"
              :config="component"
              @deleteComponents="deleteComponents"
              @dragMousedown="dragMousedown"
              @dragMouseup="dragMouseup"
              @handleSearch="handleSearch"
              @handleReset="handleReset"
            ></component>
            <!-- 提示区 -->
            <div
              :class="[
                'stash-container',
                configData.length ? '' : 'full-screen',
              ]"
              v-if="!isPreview"
              ref="stashContainer"
            >
              <span class="s-c-label"
                >请将图表从左侧拖入面板中，虚线以下不会在预览中显示</span
              >
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 全局配置弹窗 -->
    <FilterDialog
      ref="filterDialog"
      :analysis-list="analysisList"
      :temp-option="filterData"
      @setFilterConfig="setFilterConfig"
    ></FilterDialog>

    <!-- 保存 -->
    <common-edit-dialog
      title="保存"
      @cancel="closeDialog"
      @submit="submitForm"
      :button-loading="loading"
      :visible="isShowSubmitDialog"
    >
      <el-form
        ref="form"
        :model="form"
        :rules="rules"
        label-width="73px"
        @submit.native.prevent
      >
        <el-form-item label="看板名称" prop="name">
          <common-input
            v-model="form.name"
            :maxlength="16"
            placeholder="请输入"
          ></common-input>
        </el-form-item>
        <common-add-dept
          ref="commonAddDept"
          :deptListParams="departmentList"
          @getCheckedDeptId="getCheckedDeptId"
          :deptId="form.deptId"
        ></common-add-dept>
        <el-form-item label="描述" prop="kanbanDescribe">
          <common-input
            type="textarea"
            :autosize="{ minRows: 4, maxRows: 8 }"
            v-model="form.kanbanDescribe"
            placeholder="请输入"
          />
        </el-form-item>
      </el-form>
    </common-edit-dialog>
  </common-page>
</template>

<script>
import { getChartInstance } from "../../dataComponents/configuration";
import {
  queryAllTree,
  addViewBoard,
  editViewBoard,
  getInfo,
} from "@/api/screenConfig/viewboard";
import { globalList } from "@/api/screenConfig/global";
import { themeList } from "@/api/screenConfig/themeConfig";
import Container from "./components/container";
import Space from "./components/space";
import Chart from "./components/chart";
import FilterWrap from "./components/filterWrap";
import FilterDialog from "./components/filterDialog";
import { container, space, chart, filter, spaceWH } from "./dataStructure";
import { guid, getPosition, _deepClone } from "@/utils";
import ViewBoardIndex from "./mixin/ViewBoardIndex";
import { mapGetters } from "vuex";
import commonIcon from "@/components/CommonIcon";

export default {
  name: "add",
  components: {
    Container,
    Space,
    Chart,
    FilterWrap,
    FilterDialog,
    commonIcon,
  },
  mixins: [ViewBoardIndex],
  props: {
    departmentList: {
      type: Array,
      default: () => [],
    },
    typeId: {
      type: [Number, String],
    },
    typeName: {
      type: String,
    },
  },
  data() {
    return {
      isPageDataLoading: false,
      treeLoading: false,
      viewBoardName: "",
      isEdit: false,
      loading: false,
      // menuTree: [],//组件树
      menuTree: [], //组件树
      treeProp: {
        children: "children",
        label: "name",
      }, //树参数
      vbMain: null,
      vbContainer: null,
      configData: [], //看板配置数据集
      cPositionData: [], //容器位置数据集
      linePositionData: [], //间隔线位置数据集
      isPreview: false, //预览
      isShowSubmitDialog: false, //保存弹窗
      domTemp: {
        lastDomType: null,
        lastDom: null,
        nextDomType: null,
        nextDom: null,
      }, //拖拽间隔线时临时存储前后dom元素
      form: {
        id: null,
        name: null,
        kanbanDescribe: null,
        deptId: null,
        isDefault: 0,
      }, //保存弹窗表单
      rules: {
        name: [{ required: true, trigger: "blur", message: "请输入看板名称" }],
        // deptId: [{ required: true, message: '请选择组织', trigger: 'blur' }]
      },
      // themeConfig: {
      //   colors: '',
      //   themeOption: '{}'
      // },//全局主题配置
      analysisList: [], //所有图表数据集
      tempChildren: [], //暂存需要处理的数据集
      globalList: [], //全局变量
      filterData: [],
      isChange: true,
      isShowButton: false,
    };
  },
  watch: {
    configData: {
      handler: function (val) {
        this.$nextTick(() => {
          if (this.isChange) {
            this.refreshDatPosition(val);
            let analysisList = this.getAnalysisList(val).tempData;
            this.analysisList = analysisList.reduce((r, c) => {
              r.push({
                ...c,
                // variableData: c.variableData && c.variableData.length ? [...this.globalList, ...c.variableData] : this.globalList
              });
              return r;
            }, []);
            // console.log('图表数据集', this.analysisList)
            console.log("看板", this.configData);
          }
        });
      },
      deep: true,
    },
  },
  computed: {
    ...mapGetters(["systemInfo"]),
  },
  methods: {
    // 获取对应的icon
    getIcon(val) {
      let info = val ? getChartInstance(val)._info : null;
      return info ? info.icon : null;
    },
    getCheckedDeptId(val) {
      this.form.deptId = val;
    },
    /**
     * 取消保存
     */
    closeDialog() {
      this.$refs.form.resetFields();
      this.isShowSubmitDialog = false;
    },
    /**
     * 打开保存弹窗
     */
    submit() {
      this.isShowSubmitDialog = true;
    },
    /**
     * 保存
     */
    submitForm() {
      if (!this.form.name)
        return this.$GlobalErrorConfirm(
          this.isEdit ? "编辑失败" : "新增失败",
          "请输入看板名称"
        );
      let configData = this.clearFilter();
      if (!configData.length) {
        this.$GlobalErrorConfirm(
          `${this.isEdit ? "编辑失败" : "新增失败"}`,
          "至少需要拖入一个组件！"
        );
        return;
      }
      let params = {
        parentId: this.typeId,
        chartConfig: JSON.stringify(configData),
        ...this.form,
        type: this.typeId,
        componentIds: this.getAnalysisList(configData)
          .tempData.reduce((r, c) => {
            if (!r.includes(c.chartId)) {
              r.push(c.chartId);
            }
            return r;
          }, [])
          .join(","),
      };
      if (this.isEdit) {
        this.editViewBoard(params);
      } else {
        this.addViewBoard(params);
      }
    },
    /**
     * 保存新增接口
     */
    addViewBoard(params) {
      this.loading = true;
      addViewBoard(params)
        .then((res) => {
          if (res && res.code === 200) {
            this.$GlobalSuccessMessage("新增成功");
            this.isShowSubmitDialog = false;
            this.$emit("close");
            this.$emit("refresh");
          } else {
            this.$GlobalErrorConfirm("新增失败", res.msg);
          }
        })
        .finally(() => (this.loading = false));
    },
    /**
     * 保存编辑接口
     */
    editViewBoard(params) {
      this.loading = true;
      editViewBoard(params)
        .then((res) => {
          if (res && res.code === 200) {
            this.$GlobalSuccessMessage("编辑成功");
            this.isShowSubmitDialog = false;
            this.$emit("close");
            this.$emit("refresh");
          } else {
            this.$GlobalErrorConfirm("编辑失败", res.msg);
          }
        })
        .finally(() => (this.loading = false));
    },
    /**
     * 判断是否是小数，且保留几位小数
     * @param number
     * @param nums
     */
    getNumberToFix(number, nums) {
      if (
        number.toString().indexOf(".") > -1 &&
        number.toString().length - number.toString().indexOf(".") + 1 > 1
      ) {
        return parseFloat(number).toFixed(nums);
      } else {
        return parseFloat(number);
      }
    },

    //--------------------组件拖拽触发事件-------------------
    /**
     * 组件拖拽事件
     * @param item
     */
    mousedown(item) {
      //判断点击的是组件
      if (!item.leaf) return;
      let body = document.getElementsByTagName("body")[0];
      this.moveDom = this.createDom("move", false, item);

      let cpData, lpData, isInContainer, position;

      //鼠标移动时设置组件拖拽dom元素的位置
      document.onmousemove = (event) => {
        body.append(this.moveDom);
        this.moveDom.style.left = `${event.pageX}px`;
        this.moveDom.style.top = `${event.pageY}px`;
      };

      //鼠标松开清除拖拽dom元素
      document.onmouseup = (event) => {
        this.clearEvent();
      };

      //拖拽dom元素时移动事件
      //实现鼠标拖拽时获取当前鼠标落点具体位置以及显示组件落点占位符
      this.moveDom.onmousemove = (e) => {
        let x = e.pageX,
          y = e.pageY,
          { width, left, height, top } =
            this.vbContainer.getBoundingClientRect(),
          placeholder = this.createDom("placeholder", true);

        //每次移动都清除占位符
        this.clearDom("placeholder");
        cpData = null;
        lpData = null;
        isInContainer = false;

        if (x > left && x < left + width && y > top && y < height + top) {
          cpData = this.cPositionData.find(
            (f) =>
              x - left > f.x &&
              x - left < f.xWidth &&
              y - top > f.y &&
              y - top < f.yHeight
          );
          lpData = this.linePositionData.find(
            (f) =>
              x - left >= f.x &&
              x - left <= f.xWidth &&
              y - top >= f.y &&
              y - top <= f.yHeight
          );

          //先根据cpData判断当前鼠标落点是否在图表容器上
          //如果null，在根据lpData判断是否在间隔容器上
          //如果null，则表示当前落点在提示区域
          if (cpData) {
            //获取当前鼠标落点在所在容器的上下左右位置
            position = getPosition(
              x - left,
              y - top,
              cpData.x,
              cpData.y,
              cpData.xWidth,
              cpData.yHeight
            );
            //获取所在容器dom节点
            let containerDom = this.getClientRect(cpData.id);
            //设置占位符样式
            placeholder.style.cssText = `position: absolute;
                width: ${
                  position === "right" || position === "left" ? "50%" : "100%"
                };
                height: ${
                  position === "right" || position === "left" ? "100%" : "50%"
                };
                top: ${position !== "bottom" ? "0" : null};
                left: ${position !== "right" ? "0" : null};
                right: ${position === "right" ? "0" : null};
                bottom: ${position === "bottom" ? "0" : null};`;

            if (containerDom) {
              containerDom.appendChild(placeholder);
              let placeholderDom = document
                .getElementsByClassName("vb-placeholder-dom")[0]
                .getBoundingClientRect();
              placeholder.innerHTML =
                position === "right" || position === "left"
                  ? `<span>${this.getNumberToFix(
                      placeholderDom.width - spaceWH / 2,
                      2
                    )}
                   * ${this.getNumberToFix(placeholderDom.height, 2)}</span>`
                  : `<span>${this.getNumberToFix(placeholderDom.width, 2)}
                   * ${this.getNumberToFix(
                     placeholderDom.height - spaceWH / 2,
                     2
                   )}</span>`;
            }
          } else if (lpData) {
          } else {
            isInContainer = true;
            let stashDom =
              document.getElementsByClassName("stash-container")[0];
            placeholder.style.height = `${container.height}px`;
            if (this.vbContainer && stashDom) {
              stashDom.parentNode.insertBefore(placeholder, stashDom);
              let placeholderDom = document
                .getElementsByClassName("vb-placeholder-dom")[0]
                .getBoundingClientRect();
              placeholder.innerHTML = `<span>${placeholderDom.width} * ${placeholderDom.height}</span>`;
            }
          }
        }
      };
      this.moveDom.onmouseup = (e) => {
        //由于位置信息是watch监听，必须在移动dom元素松开时清除占位符元素，防止计算位置时把占位符高度也算在内
        this.clearDom("moveDom");
        this.clearDom("placeholder");

        if (cpData) {
          //鼠标落点在图表上进行插入
          let configData = cpData ? this.getConfigDataById(cpData.id) : null;
          this.insertDataInChart("inChart", item, position, cpData, configData);
        } else if (lpData) {
          //鼠标落点在分割线处进行插入
        } else if (isInContainer) {
          //鼠标落点再区域范围内进行插入
          this.insertDataInChart("inContainer", item);
        }
      };
    },
    /**
     * 鼠标松开事件
     */
    mouseup() {
      this.clearEvent();
    },
    /**
     * 清除拖拽事件
     */
    clearEvent() {
      document.onmousedown = undefined;
      document.onmousemove = undefined;
      document.onmouseup = undefined;
    },
    /**
     * 根据类型创建临时dom元素
     * @params type 拖拽元素:move  占位符:placeholder  百分比:percent
     * @params isTransparent  是否需要透明背景
     * @params info
     */
    createDom(type, isTransparent, info) {
      let dom = document.createElement("div");
      dom.classList.add(`vb-${type}-dom`);
      if (type === "move") {
        dom.style.borderColor = this.systemInfo.theme;
        dom.innerHTML = info.name || info.chartName;
      } else if (type === "placeholder") {
        if (!isTransparent) {
          let nodeDom = document.createElement("div");
          nodeDom.classList.add(`vb-${type}-node-dom`);
          dom.appendChild(nodeDom);
          dom.style.opacity = "1";
          dom.style.background = "#fff";
          // console.log(dom)
        }
        dom.style.top = 0;
        dom.style.width = "100%";
        dom.style.height = "100%";
      }
      return dom;
    },
    /**
     * 清除占位符，拖拽时等临时创建的dom元素
     * @params type  拖拽元素:moveDom  占位符:placeholder
     */
    clearDom(type) {
      if (type === "moveDom" && this.moveDom) {
        let body = document.getElementsByTagName("body")[0];
        body.removeChild(this.moveDom);
        this.moveDom = null;
      } else {
        let placeholder = document.getElementsByClassName(`vb-${type}-dom`);
        if (placeholder && placeholder.length) {
          for (let i = placeholder.length - 1; i >= 0; i--) {
            placeholder[i].remove();
          }
        }
      }
    },
    /**
     * 根据id获取配置数据中某一个组件数据
     * @param id
     * @param configData
     * @returns {{childrenList:null, index: null, common: null,}}
     */
    getConfigDataById(id, configData) {
      let data = configData ? configData : this.configData;
      let result = {
        childrenList: null, //数据children集合，方便后续在集合中插入数据
        index: null, //查询数据在children的下标
        data: null, //查询的数据
      };
      if (data.length) {
        data.forEach((f, index) => {
          if (f.id === id) {
            result.data = f;
            result.index = index;
            result.childrenList = data;
          }
          if (!result.data && f.children && f.children.length) {
            result = this.getConfigDataById(id, f.children);
          }
        });
      }
      return result;
    },
    /**
     * 刷新各容器位置信息
     * @param configData
     */
    refreshDatPosition(configData) {
      this.cPositionData = [];
      this.linePositionData = [];

      //获取所有图表dom元素集合
      let bcDom = document.getElementsByClassName("board-chart-wrap");
      //获取所有间隔线dom元素集合
      let spaceDom = document.getElementsByClassName("board-space-wrap");
      //获取最外层dom滚动高度
      let scrollTop = this.vbContainer.scrollTop;

      const saveData = (data, list) => {
        for (let i = 0; i < data.length; i++) {
          let { offsetLeft, offsetTop, offsetWidth, offsetHeight, dataset } =
            data[i];
          list.push({
            id: dataset.id,
            x: offsetLeft,
            y: offsetTop - scrollTop,
            xWidth: offsetLeft + offsetWidth,
            yHeight: offsetTop + offsetHeight - scrollTop,
          });
        }
      };
      //重新获取每个图表组件的位置信息
      if (bcDom && bcDom.length) {
        saveData(bcDom, this.cPositionData);
      }
      //重新获取每个间隔线组件的位置信息
      if (spaceDom && spaceDom.length) {
        saveData(spaceDom, this.linePositionData);
      }
    },
    /**
     * 计算容器中有几条分割线、分割线的宽度的和
     * @param data []
     * @param index 指定下标
     * @returns {{
     *   spaceOffset: number,  分割线宽度和
     *   spaceIdx: number      分割线数量
     * }}
     */
    getSpaceData(data, index) {
      let spaceIdx = 0,
        spaceOffset = 0,
        childrenList = data.childrenList;
      for (let i = 0; i < childrenList.length; i++) {
        if (childrenList[i].type === "space") {
          spaceIdx++;
        } else {
          if (!index) {
            i !== data.index &&
              (spaceOffset = spaceOffset + childrenList[i].space);
          } else {
            if (
              i !== data.index &&
              i !== index &&
              childrenList[i].type !== "board-space"
            ) {
              spaceOffset = spaceOffset + childrenList[i].space;
            }
          }
        }
      }
      return { spaceIdx, spaceOffset };
    },
    /**
     * 获取数据中图表的数量
     * @params common
     * @returns {number}
     */
    getChartNums(data) {
      let nums = 0;
      data.forEach((f) => {
        if (f.type === "chart" || f.type === "container") {
          nums++;
        }
      });
      return nums;
    },
    /**
     * 根据id获取dom元素信息
     * @params id
     */
    getClientRect(id) {
      return document.querySelectorAll(`[data-id="${id}"]`)[0];
    },
    /**
     * 根据位置插入图表容器，或者插入container容器且修改数据结构
     * @param type 落点在图表处: inChart   落点在分割线处: inSpace  落点在其他指定区域范围内: inContainer
     * @param chartInfo  chartInfo
     * @param p   position       鼠标位于容器的上下左右位置
     * @param cp  chartPosition  所处容器的具体位置
     * @param cd  configData     所处容器的数据结构
     */
    insertDataInChart(type, chartInfo, p, cp, cd) {
      //定义每个容器的id
      let chartId = guid(),
        containerId = guid(),
        spaceId = guid(),
        relativeId = guid(),
        chartForm = {
          chartId: chartInfo.id, //图表id
          chartName: chartInfo.name, //图表名称
          chartType: chartInfo.chartType, //图表类型
          variableConfig: [], //变量值
          selectVarName: "",
          selectVarName2: "",
        };

      if (type === "inChart") {
        let children = cd.childrenList,
          index = cd.index,
          item = cd.data,
          direction = p === "right" || p === "left" ? "TD" : "LD";

        // 判断需要插入的图表容器大小是否大于设定的最小高度和最大高度
        let { height, width } = this.getClientRect(
          item.id
        ).getBoundingClientRect();
        if (
          (direction === "LD" && (height - spaceWH) / 2 <= item.minHeight) ||
          (direction === "TD" && (width - spaceWH) / 2 <= item.minWidth)
        ) {
          return this.$message({
            type: "warning",
            message: `容器宽高不能小于 ${item.minWidth} * ${item.minHeight}!`,
          });
        }

        //根据鼠标位于容器的上下左右位置插入新的图表
        //先判断所处容器数据中的direction
        //如果需要插入的图表容器（横向插入或者纵向插入）和direction方向一直，则在图表容器数据前后直接添加新的图表容器
        //如果方向不一致，需要先创建新的container容器，将当前所处的图表容器放入container容器中，并且添加新的图表容器
        if (direction === cd.data.direction) {
          //计算当前容器中有几条分割线
          //根据公式计算需要切割的图表容器的间距和新添加的图表容器的间距
          //修改当前图表容器的高度/宽度
          let { spaceIdx, spaceOffset } = this.getSpaceData(cd);
          item.space = (space.spaceWH * (spaceIdx + 1) - spaceOffset) / 2;
          if (direction === "TD") {
            item.width = item.width / 2;
          } else {
            item.height = item.height / 2;
          }
          item.relativeId = relativeId;
          //创建新的图表容器
          let newChart = {
            ...chart,
            id: chartId,
            direction: item.direction,
            parentId: item.parentId,
            width: item.width,
            height: item.height,
            space: item.space,
            relativeId: relativeId,
            chartForm,
          };
          //创建新的分割线容器
          let newSpace = {
            ...space,
            id: spaceId,
            direction: p === "right" || p === "left" ? "LD" : "TD",
            parentId: item.parentId,
          };
          //插入操作
          p === "right"
            ? children.splice(index + 1, 0, newSpace, newChart)
            : children.splice(index, 0, newChart, newSpace);
        } else {
          //创建新的父容器
          let newContainer = {
            ...container,
            id: containerId,
            direction,
            lastDirection: item.direction,
            parentId: item.parentId,
            space: item.space,
            width: item.width,
            height: item.height,
            children: [],
          };
          //保存旧的图表容器，并且修改其parentId
          let oldChart = {
            ...item,
            direction,
            parentId: containerId,
            width: direction === "TD" ? item.width / 2 : 100,
            height: direction === "TD" ? 100 : item.height / 2,
            relativeId: relativeId,
          };
          //创建新的图表容器
          let newChart = {
            ...chart,
            id: chartId,
            direction,
            parentId: containerId,
            space: space.spaceWH / 2,
            width: oldChart.width,
            height: oldChart.height,
            relativeId: relativeId,
            chartForm,
          };
          //创建分割线容器
          let newSpace = {
            ...space,
            id: spaceId,
            direction: p === "right" || p === "left" ? "LD" : "TD",
            parentId: containerId,
          };

          if (p === "left" || p === "top") {
            newContainer.children.push(newChart, newSpace, oldChart);
          } else {
            newContainer.children.push(oldChart, newSpace, newChart);
          }
          //插入操作
          children.splice(index, 1, newContainer);
        }
      } else if (type === "inContainer") {
        this.configData.push(
          {
            ...container,
            id: containerId,
            isOutContainer: true,
            children: [
              {
                ...chart,
                id: chartId,
                height: 100,
                parentId: containerId,
                chartForm,
              },
            ],
          },
          {
            ...space,
            id: spaceId,
          }
        );
      }
      this.isShowButton = true;
    },
    /**
     * 删除组件
     * @param configInfo
     * 根据configInfo中type确定是否删除的是图表组件
     * 当一个container容器中存在超过2个图表容器时，或者超过1个图表容器并且父容器为最外层容器时，可以直接删除选中图表，并且修改相邻图表容器宽度
     * 当container容器内有且只有2个图表容器，需要先找到2个图表的父container容器，删除选中容器的同时，将父container容器的数据改为相邻图表容器的数据
     * 当有且只有1个图表容器，且容器的父container容器为最外层时，直接删除父容器以及相邻分割线
     */
    deleteComponents(configInfo) {
      if (configInfo.type === "filterWrap") {
        this.configData.splice(0, 1);
        this.filterData = [];
        return;
      }
      //获取当前chart图表数据
      let cd = this.getConfigDataById(configInfo.id),
        children = cd.childrenList,
        index = cd.index,
        item = cd.data;

      //获取再上一级的容器数据
      let cdParent = this.getConfigDataById(item.parentId),
        chartNum = this.getChartNums(children);

      //超过2个图表容器，或者超过1个图表容器并且父容器为最外层容器时
      if (chartNum > 2 || (chartNum > 1 && cdParent.data.isOutContainer)) {
        let i = children.findIndex(
          (f, i) => f.relativeId === item.relativeId && i !== index
        );
        i = i > -1 ? i : index === children.length - 1 ? index - 2 : index + 2;
        //计算当前排除选中的图表，剩下图表的space的和
        let { spaceIdx, spaceOffset } = this.getSpaceData(cd, index);
        //修改相邻容器的数据
        children[i].width =
          item.direction === "TD"
            ? children[i].width + item.width
            : children[i].width;
        children[i].height =
          item.direction === "LD"
            ? children[i].height + item.height
            : children[i].height;
        children[i].space =
          (spaceIdx - 1) * space.spaceWH - (spaceOffset - children[i].space);
        // children[i] = {
        //   ...children[i],
        //   width: item.direction === 'TD' ? children[i].width + item.width : children[i].width,
        //   height: item.direction === 'LD' ? children[i].height + item.height : children[i].height,
        //   space: (spaceIdx - 1) * space.spaceWH - (spaceOffset - children[i].space)
        // }

        children.splice(index === 0 ? index : index - 1, 2);
      } else {
        //判断父容器是否是外层容器
        //如果是，直接把父容器连同分割线删掉
        //如果不是，则将未选中的图表容器覆盖在父容器上，但是部分数值需要继承父容器的数据
        if (cdParent.data.isOutContainer) {
          cdParent.childrenList.splice(cdParent.index, 2);
        } else {
          let cdpData = cdParent.data;
          let otherChart = children.find(
            (f, i) => i !== index && f.type !== "space"
          );
          //继承父容器的部分数值
          otherChart = {
            ...otherChart,
            width: cdpData.width,
            height: cdpData.height,
            parentId: cdpData.parentId,
            space: cdpData.space,
            direction: cdpData.lastDirection,
            lastDirection: cdpData.lastDirection,
          };
          //删除并插入
          cdParent.childrenList.splice(cdParent.index, 1, otherChart);
        }
      }
      //删除筛选器中相关chart容器组件的配置项
      this.changeFilterConfig(configInfo.id);
      this.isShowButton = true;
    },

    /**
     * 组件内部拖拽（分割线拖拽 / 组件拖拽）
     * @param type   分割线: space  组件: chart
     * @param configInfo
     */
    dragMousedown(type, configInfo) {
      if (type === "space") {
        this.dragSpaceMousedown(configInfo);
      } else {
        this.dragChartMousedown(configInfo);
      }
    },
    /**
     * 拖拽chart组件，互换位置
     * @param configInfo
     */
    dragChartMousedown(configInfo) {
      let body = document.getElementsByTagName("body")[0],
        cpData,
        cd = this.getConfigDataById(configInfo.id);

      this.moveDom = this.createDom("move", false, configInfo.chartForm);

      //鼠标移动事件
      document.onmousemove = (event) => {
        //清除占位符
        this.clearDom("placeholder");

        //设置拖拽容器的遮罩
        let dragDom = this.getClientRect(configInfo.id);
        if (dragDom) {
          let dragPlaceholder = this.createDom("placeholder", false);
          dragPlaceholder.style.position = "absolute";
          dragPlaceholder.style.zIndex = "2022";
          dragPlaceholder.firstChild.innerHTML = "请拖拽组件且放置于目标容器";
          dragDom.appendChild(dragPlaceholder);
        }

        //添加拖拽提示
        body.append(this.moveDom);
        this.moveDom.style.left = `${event.pageX}px`;
        this.moveDom.style.top = `${event.pageY}px`;

        let x = event.pageX,
          y = event.pageY,
          { width, left, height, top } =
            this.vbContainer.getBoundingClientRect();
        //寻找目标容器位置
        if (x > left && x < left + width && y > top && y < height + top) {
          cpData = this.cPositionData.find(
            (f) =>
              x - left > f.x &&
              x - left < f.xWidth &&
              y - top > f.y &&
              y - top < f.yHeight &&
              f.id !== configInfo.id
          );

          if (cpData && cpData.id !== configInfo.id) {
            //获取所在容器dom节点
            let containerDom = this.getClientRect(cpData.id);
            if (containerDom) {
              //设置占位符样式
              let placeholder = this.createDom("placeholder", true);
              placeholder.style.cssText =
                "position: absolute; top: 0; width: 100%; height: 100%";
              placeholder.innerHTML = "目标容器";
              containerDom.appendChild(placeholder);
            }
          }
        }
      };
      //鼠标松开事件
      document.onmouseup = (e) => {
        if (cpData) {
          let cpConfigData = this.getConfigDataById(cpData.id).data;
          let sourceTemp = _deepClone(cd.data);
          this.$nextTick(() => {
            cd.data.chartForm = { ...cpConfigData.chartForm };
            cpConfigData.chartForm = { ...sourceTemp.chartForm };
          });
        }
        this.clearEvent();
        this.clearDom("moveDom");
        this.clearDom("placeholder");
      };
    },
    /**
     * 拖拽分割线
     * 先计算鼠标拖拽偏移百分比:(拖拽后的位置 - 鼠标初始点击分割线时的位置) / 分割线所在父容器的宽度 * 100
     * 再用图表宽度减去偏移百分比
     */
    dragSpaceMousedown(configInfo) {
      let cd = this.getConfigDataById(configInfo.id),
        children = cd.childrenList,
        i = cd.index;

      this.tempChildren = children;
      //获取当前分割线所在父容器dom元素、相邻前一个图表dom元素
      let pDom = this.getClientRect(cd.data.parentId);
      this.domTemp.lastDomType = children[i - 1].type === "container";
      this.domTemp.lastDom = this.getClientRect(children[i - 1].id);

      //获取对应dom元素的宽高
      let { width: ldw, height: ldh } =
          this.domTemp.lastDom.getBoundingClientRect(),
        {
          width: lw,
          height: lh,
          minWidth: lmw,
          minHeight: lmh,
        } = children[i - 1];

      //判断pDom是否为undefined，是则表明当前拖拽dom元素为最外层，不影响后一个相邻容器
      //否则表明拖拽改变左右或者上下相邻图表容器大小，则获取后一个相邻容器的相关数据
      let nextDomInfo, width, height;
      if (pDom) {
        width = pDom.getBoundingClientRect().width;
        height = pDom.getBoundingClientRect().height;
        this.domTemp.nextDomType = children[i + 1].type === "container";
        this.domTemp.nextDom = this.getClientRect(children[i + 1].id);
        nextDomInfo = {
          ndw: this.domTemp.nextDom.getBoundingClientRect().width,
          ndh: this.domTemp.nextDom.getBoundingClientRect().height,
          nw: children[i + 1].width,
          nh: children[i + 1].height,
          nmw: children[i + 1].minWidth,
          nmh: children[i + 1].minHeight,
        };
      }

      let spaceDom = this.getClientRect(children[i].id);

      //创建占位符dom元素
      let placeholder = this.createDom("placeholder", false);
      placeholder.style.position = "absolute";
      placeholder.style.zIndex = "99";

      let oldPageX, oldPageY;
      //鼠标按下事件
      document.onmousedown = (event) => {
        // console.log('---->按下触发', this.domTemp.lastDom)

        if (pDom) {
          this.setChartIsMove([children[i + 1]], true);
        }
        this.setChartIsMove([children[i - 1]], true);
        oldPageX = event.pageX;
        oldPageY = event.pageY;

        //当上一节点或者下一节点为类型container时修改position属性
        if (this.domTemp.lastDomType) {
          this.domTemp.lastDom.style.position = "relative";
        }
        if (this.domTemp.nextDomType) {
          this.domTemp.nextDom.style.position = "relative";
        }

        //添加占位符
        this.domTemp.lastDom.appendChild(placeholder.cloneNode(true));
        pDom && this.domTemp.nextDom.appendChild(placeholder.cloneNode(true));
      };
      //鼠标移动事件
      document.onmousemove = (event) => {
        event.preventDefault();
        if (cd.data.direction === "LD") {
          //横向拖拽时
          //计算鼠标偏移量
          let offsetPageX = event.pageX - oldPageX;
          //判断是否大于容器最小宽度
          if (
            ldw + offsetPageX > lmw &&
            nextDomInfo.ndw - offsetPageX > nextDomInfo.nmw
          ) {
            //前后图表容器宽度加减百分比
            children[i - 1].width = lw + (offsetPageX / width) * 100;
            children[i + 1].width =
              nextDomInfo.nw - (offsetPageX / width) * 100;
            this.setPercentDom(children, i, "LD", spaceDom);
          }
        } else {
          //纵向拖拽时
          let offsetPageY = event.pageY - oldPageY;
          if (pDom) {
            if (
              ldh + offsetPageY > lmh &&
              nextDomInfo.ndh - offsetPageY > nextDomInfo.nmh
            ) {
              //前后图表容器高度加减百分比
              children[i - 1].height = lh + (offsetPageY / height) * 100;
              children[i + 1].height =
                nextDomInfo.nh - (offsetPageY / height) * 100;
            }
          } else {
            if (ldh + offsetPageY > lmh) {
              children[i - 1].height = lh + offsetPageY;
            }
          }
          this.setPercentDom(children, i, "TD", spaceDom);
        }
      };
      this.isShowButton = true;
      document.onmouseup = (event) => {
        this.dragMouseup();
      };
    },
    /**
     * 鼠标拖拽松开事件
     */
    dragMouseup() {
      // console.log('---->松开触发')
      this.setChartIsMove(this.tempChildren, false);
      //恢复组件原有position属性
      if (this.domTemp.lastDomType) {
        this.domTemp.lastDom.style.position = "static";
      }
      if (this.domTemp.nextDomType) {
        this.domTemp.nextDom.style.position = "static";
      }
      this.clearDom("placeholder");
      this.clearDom("percent");
      this.clearEvent();
    },
    /**
     * 设置前后容器百分比显示
     * @param parentData 父节点数据
     * @param index      当前分割线所在数据中的下标
     * @param direction  方向
     * @param spaceDOm   分割线dom
     */
    setPercentDom(parentData, index, direction, spaceDOm) {
      // 清除显示
      this.clearDom("percent");
      // 添加百分比dom节点
      let percentL = this.createDom("percent", false),
        space = "16px";

      if (direction === "LD") {
        percentL.style.right = space;
        percentL.style.top = "calc(50% - 10px)";
      } else {
        percentL.style.bottom = space;
        percentL.style.right = "calc(50% - 30px)";
      }

      //计算每个容器占比
      let total = 100,
        num = 0;
      parentData.map((f, i) => {
        if (f.type !== "space") {
          if (i === parentData.length - 1) {
            return (f.percent = total);
          }
          f.percent = f[direction === "LD" ? "width" : "height"].toFixed(1);
          total = (total - f.percent).toFixed(1);
        }
      });

      percentL.innerHTML =
        direction === "LD"
          ? `${parentData[index - 1].percent}%`
          : parentData[index].parentId
          ? `${parentData[index + 1].percent}%`
          : `${parentData[index - 1].percent}px`;

      // percentL.innerHTML = `${
      //   parentData[direction === "LD" ? index - 1 : index + 1].percent
      // }${parentData[index].parentId ? "%" : "px"}`;
      spaceDOm.appendChild(percentL);

      if (parentData[index].parentId) {
        let percentR = percentL.cloneNode(true);
        if (direction === "LD") {
          percentR.style.left = space;
        } else {
          percentL.style.top = space;
        }
        percentR.innerHTML = `${
          parentData[direction === "LD" ? index + 1 : index - 1].percent
        }%`;
        spaceDOm.appendChild(percentR);
      }
    },
    /**
     * 修改数据集中isMove属性
     * @param data [] 需要遍历的数据集
     * @param isMove true / false
     */
    setChartIsMove(data, isMove) {
      if (!data.length) return;
      data.forEach((item) => {
        if (item.type !== "space") {
          item.isMove = isMove;
        }
        if (item.children && item.children.length) {
          this.setChartIsMove(item.children, isMove);
        }
      });
    },
    /**
     *  获取数组中类型为图表的数据集以及数量
     *  @param data [] 需要遍历的数据集
     *  @param nums 计算开始值
     *  @param temp [] 满足条件的数据集
     */
    getAnalysisList(data, nums, temp) {
      let result = {
        tempData: temp ? temp : [],
        total: nums ? nums : 0,
      };
      data.forEach((f) => {
        if (f.type === "chart") {
          result.total++;
          result.tempData.push({
            id: f.id,
            ...f.chartForm,
            // variableData: f.variableData ? JSON.parse(f.variableData) : []
          });
        }
        if (f.children && f.children.length > 0) {
          result = this.getAnalysisList(
            f.children,
            result.total,
            result.tempData
          );
        }
      });
      return result;
    },
    /**
     * 设置AI布局
     */
    setAILayout() {
      if (!this.isShowButton) {
        return;
      }
      let setWh = (configData, key) => {
        if (configData.length) {
          let nums = configData.filter((f) => f.type !== "space").length;
          configData.forEach((item) => {
            if (item.type !== "space" && key) {
              item[key] = 100 / nums;
            }
            if (item.children) {
              setWh(
                item.children,
                item.direction === "TD" ? "width" : "height"
              );
            }
          });
        }
      };
      setWh(this.configData);
      this.$GlobalSuccessMessage("操作成功");
      this.isShowButton = false;
    },

    //--------------------全局配置触发事件-------------------
    /**
     * 打开全局配置弹窗
     */
    setGlobalOption() {
      this.$nextTick(() => {
        this.$refs.filterDialog.showDialog();
      });
    },

    /**
     * 全局配置弹窗回调事件
     */
    setFilterConfig(info) {
      this.filterData = info;
      if (!this.configData.length || this.configData[0].type !== "filterWrap") {
        this.configData.unshift({
          ...filter,
          id: guid(),
          children: info,
        });
      } else {
        this.configData[0].children = info;
      }
    },
    /**
     * 根据chartId修改筛选器组件数据
     * @params chartContainerId 图表容器id
     */
    changeFilterConfig(chartContainerId) {
      if (this.configData.length && this.configData[0].type === "filter") {
        this.configData[0].children.forEach((f) => {
          let i = f.analysisList.findIndex((m) => m.id === chartContainerId);
          if (i > -1) f.analysisList.splice(i, 1);
        });
      }
    },

    //--------------------页面初始获取数据-------------------
    /**
     * 获取全局变量
     */
    getGlobalList() {
      globalList({}).then((res) => {
        if (res && res.code === 200) {
          this.globalList = res.rows.reduce((r, c) => {
            r.push({
              name: c.name,
              type: c.type,
              defaultValue: c.defaultValue,
              variableType: "全部",
            });
            return r;
          }, []);
        } else {
          this.globalList = [];
        }
      });
    },
    // /**
    //  * 获取主题配置数据
    //  */
    // getThemeList() {
    //   themeList().then((res) => {
    //     if (res && res.code === 200) {
    //       if (res.rows.length) {
    //         this.themeConfig.themeOption = res.rows[0].themeOption ? res.rows[0].themeOption : ''
    //         this.themeConfig.colors = res.rows[0].colors ? res.rows[0].colors : ''
    //       }
    //     }
    //   })
    // },
    /**
     * 获取报表组件树结构
     */
    queryAllTree() {
      const handleKey = (array) => {
        return array.map((item) => {
          item.key = item.id;
          if (
            item.children &&
            item.children instanceof Array &&
            item.children.length
          )
            item.children = handleKey(item.children);
          return item;
        });
      };
      this.treeLoading = true;
      queryAllTree({})
        .then((res) => {
          if (res && res.code === 200) {
            let list = handleKey(res.data);
            this.menuTree = [
              {
                id: "0-1",
                key: "0",
                isDefault: 1,
                isRoot: true,
                name: "预定义",
                sort: 1,
                children: list.filter((item) => item.isDefault === 1),
              },
              {
                id: "0-0",
                key: "0",
                isDefault: 0,
                isRoot: true,
                name: "自定义",
                sort: 2,
                children: list.filter((item) => item.isDefault === 0),
              },
            ];
          } else {
            this.menuTree = [];
          }
        })
        .finally(() => (this.treeLoading = false));
    },
    /**
     * 数据初始化
     * @param info
     */
    initData(info) {
      this.isEdit = true;
      this.viewBoardName = info.name ? info.name : "";
      this.isPageDataLoading = true;
      getInfo({ id: info.id })
        .then((res) => {
          if (res && res.code === 200) {
            if (res.data) {
              Reflect.ownKeys(this.form).forEach(
                (k) => (this.form[k] = res.data[k])
              );
              // console.log(this.form);
              try {
                this.configData = JSON.parse(res.data.chartConfig);
                this.filterData =
                  this.configData.length &&
                  this.configData[0].type === "filterWrap"
                    ? this.configData[0].children
                    : [];
                // console.log('筛选器--->', this.filterData)
              } catch (e) {
                console.err("转换异常: ", e);
                this.configData = null;
              }
            }
          } else {
            this.$GlobalErrorConfirm("获取数据失败", res.msg, "重新加载").then(
              () => {
                this.initData(info);
              }
            );
          }
        })
        .finally(() => (this.isPageDataLoading = false));
    },
  },

  mounted() {
    // this.getThemeList()
    this.getGlobalList();
    this.vbMain = this.$refs.vbMain.getBoundingClientRect();
    this.vbContainer = this.$refs.vbContainer;

    this.vbContainer.addEventListener("scroll", () => {
      this.refreshDatPosition();
    });
    this.queryAllTree();
  },
};
</script>

<style scoped lang="scss">
@import "./components/styles.scss";

.add-view-board-wrap {
  width: 100%;
  height: 100%;
  display: flex;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-size: 14px;

  .component-tree-wrap {
    width: 220px;
    height: 100%;
    box-sizing: border-box;
    border-right: 1px solid #eee;

    .component-tree-label {
      font-size: 14px;
      font-weight: bold;
      padding: 12px 16px;
      box-sizing: border-box;
    }

    .component-tree {
      width: 100%;
      height: calc(100% - 44px);
      padding: 0 16px 12px 16px;
      box-sizing: border-box;

      .tree-node-item {
        display: flex;
        align-items: center;

        svg {
          width: 14px;
          height: 14px;
          margin-right: 4px;
        }
      }
    }
  }

  .view-board-right-wrap {
    width: calc(100% - 220px) !important;
  }

  .view-board-main {
    height: calc(100% - 36px) !important;
  }

  .config-btn-disabled {
    color: #c0c4cc;
    cursor: not-allowed;
  }
}
</style>

<style lang="scss">
.vb-move-dom {
  font-size: 14px;
  position: fixed;
  user-select: none;
  box-sizing: border-box;
  z-index: 1199;
  background-color: #eff2f6;
  transform: translate(-50%, -50%);
  border: 1px dashed #409eff;
  padding: 6px;
  color: #606266;
  border-radius: 4px;
}

.vb-placeholder-dom {
  /*position: absolute;*/
  z-index: 2012;
  background: rgba(245, 34, 45, 0.1);
  //opacity: .1;
  color: #606266;
  font-size: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.vb-percent-dom {
  /*position: absolute;*/
  z-index: 2012;
  background: rgba(0, 0, 0, 0.3);
  position: absolute;
  opacity: 1;
  width: 60px;
  height: 20px;
  line-height: 20px;
  color: #ffffff;
  text-align: center;
  border-radius: 6px;
}

.vb-placeholder-node-dom {
  background: rgba(245, 34, 45, 0.1);
  position: absolute;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  //opacity: .1;
}

.header-edit-name {
  display: flex;
  align-items: center;

  .line {
    color: #303133;
    margin-right: 18px;
  }

  .label {
    width: 100px;
    color: #303133;
    font-size: 15px;
  }
}
</style>
